﻿using Microsoft.AspNetCore.SignalR;
using MultiAgentsClient.ChatDataStructures;
using MultiAgentsClient.Shared;
using MultiAgentsServer.Scripts.Database;
using MultiAgentsServer.Scripts.Hubs;
using MultiAgentsServer.Scripts.Services.DataModels;
using MultiAgentsServer.Scripts.Shared;
using MultiAgentsServer.Scripts.Tools;
using MultiAgentsShared;
using Newtonsoft.Json;
using OpenAI.ObjectModels;
using OpenAI.ObjectModels.RequestModels;
using SixLabors.ImageSharp;
using System.Text;
using static MultiAgentsServer.Scripts.AgentTemplates.AgentTemplates;
using static MultiAgentsServer.Scripts.Shared.ModelConsts;

namespace MultiAgentsServer.Scripts.Services;

public class ChatContextManager : ManagerBase
{
    //Dependency registration
    private readonly UserCookieDataModel userCookieDataModel;
    private readonly DataBaseOperationService dataBaseOperationService;
    private readonly IHubContext<SignalRChatHub> signalRChatHubContext;
    private readonly IHubContext<SignalRUserSettingsHub> userSettingsHubContext;
    private readonly LlmComEntryPoint llmComEntryPoint;

    // connectId, cancellationTokenSource
    private readonly Dictionary<string, CancellationTokenSource> _cancellationTokenSources = [];
    private readonly float truncationThreshold = 0.7F;

    public ChatContextManager(IEventBus eventBus,
        LlmComEntryPoint llmComEntryPoint,
        UserCookieDataModel userCookieDataModel,
        DataBaseOperationService dataBaseOperationService,
        IHubContext<SignalRChatHub> signalRChatHubContext,
        IHubContext<SignalRUserSettingsHub> userSettingsHubContext) : base()
    {
        this.llmComEntryPoint = llmComEntryPoint;
        this.userCookieDataModel = userCookieDataModel;
        this.dataBaseOperationService = dataBaseOperationService;
        this.signalRChatHubContext = signalRChatHubContext;
        this.userSettingsHubContext = userSettingsHubContext;

        eventBus.Subscribe<SendUserMsgCommand>(command => { Task.Run(() => SendUserMsgRequestHandlerAsync(command)); });
        eventBus.Subscribe<ResendMessageCommand>(command => { Task.Run(() => ResendMessageCommandHandlerAsync(command)); });
        eventBus.Subscribe<OverrideMessageCommand>(command => { Task.Run(() => OverrideMessageCommandHandlerAsync(command)); });
        eventBus.Subscribe<SendUserMessageWithoutLLMResponseCommand>(command => { Task.Run(() => SendUserMessageWithoutLLMResponseRequestHandlerAsync(command)); });
        eventBus.Subscribe<SendNodeMessageCommand>(command => { Task.Run(() => SendNodeMessageRequestHandlerAsync(command)); });
        eventBus.Subscribe<StopUserMsgCommnad>(command => { Task.Run(() => StopUserMsgRequestHandler(command)); });
        eventBus.Subscribe<SendToolResultCommand>(command => { Task.Run(() => SendToolCallResultRequestHandlerAsync(command)); });
        eventBus.Subscribe<GetFileContentCommand>(command => { Task.Run(() => GetFileContentRequestHandlerAsync(command)); });
    }

    private int CreateAgentInstanceIfNotExist(int chatRoomId, AgentTemplateDB agentTemplate)
    {
        int agentInstanceId = dataBaseOperationService.GetAgentInstanceIdInRoom(chatRoomId, agentTemplate.Id);

        if (agentInstanceId == -1)
        {
            agentInstanceId = dataBaseOperationService.AddAgentToChatRoom(chatRoomId, agentTemplate.Id, agentTemplate.LLMModel, agentTemplate.Temperature, agentTemplate.AllowedTools);
        }

        return agentInstanceId;
    }

    private bool CheckModelAuthorization(int userId, string modelName)
    {
        List<string> accessibleLlmModels = dataBaseOperationService.FetchUserAccessibleLlmModels(userId);

        return accessibleLlmModels.Contains(modelName);
    }

    private List<ChatMessageData> CutMessages(List<ChatMessageData> chatMessages, int maxTokens)
    {
        int tokens = 0;
        int indexToStartPreserving = -1;

        for (int i = chatMessages.Count - 1; i >= 0; i--)
        {
            ChatMessageData message = chatMessages[i];

            if (message.content != null)
            {
                tokens += message.tokens;
            }

            if (tokens > maxTokens * truncationThreshold)
            {
                if (i == chatMessages.Count - 1)
                {
                    return [];
                }
                for (int j = i + 1; j < chatMessages.Count; j++)
                {
                    if (chatMessages[j].role == StaticValues.ChatMessageRoles.User)
                    {
                        indexToStartPreserving = j;
                        break;
                    }
                }
                break;
            }
        }

        if (indexToStartPreserving == -1)
        {
            return chatMessages;
        }

        for (int i = 0; i < indexToStartPreserving - 1;)
        {
            if (chatMessages[i].role != StaticValues.ChatMessageRoles.System)
            {
                chatMessages.RemoveAt(i);
                indexToStartPreserving--;
            }
            else
            {
                i++;
            }
        }

        return chatMessages;
    }

    private async Task ProvideAssistentOptionsIfNotToolCall(string connectionId, int chatRoomId, List<ChatMessageData> chatMessages, string lastAssistantMessage, int aiMsgRequired = 4)
    {
        //extract last 4 ai messages, and predict what user wants
        List<LlmMessageSimp> llmMessages = [];
        int aiMsgCounter = 0;
        for (int i = chatMessages.Count - 1; i >= 0; i--)
        {
            if (chatMessages[i].role == StaticValues.ChatMessageRoles.Assistant)
            {
                if (aiMsgCounter > aiMsgRequired)
                {
                    break;
                }
                aiMsgCounter++;
            }

            llmMessages.Insert(0, new LlmMessageSimp(chatMessages[i].role, chatMessages[i].content));
        }

        llmMessages.Insert(0, new LlmMessageSimp(StaticValues.ChatMessageRoles.Assistant, lastAssistantMessage));

        string chatHistory = JsonConvert.SerializeObject(llmMessages);

        LlmStreamSlice? response;
        string llmModel = DefaultModelConfigModel.DefaultModelConfig.ReplyAssistantModel;
        response = await llmComEntryPoint.RequestChatSimp(PromptMessagesConsts.ReplyAssistantOptions(chatHistory), llmModel);

        if (response == null || response.message == null)
        {
            return;
        }

        try
        {
            List<string> optionsList = response.message.Split(new char[] { ';', '；'}, StringSplitOptions.RemoveEmptyEntries).ToList();

            await SignalRChatHub.ReplyAssistantOptionsResponse(signalRChatHubContext, connectionId, chatRoomId, optionsList);
        }
        catch (Exception e)
        {
            Debug.LogError(e?.ToString() ?? "Unknown error");
        }
    }

    private async Task RenameRoomIfHaveNot(int userId, int chatRoomId, List<ChatMessageData> chatMessageDatas)
    {
        ChatRoomDB chatRoomDb = dataBaseOperationService.GetChatRoom(chatRoomId) ?? throw Debug.ShouldNotHappen();

        if (chatRoomDb.AIRenamed == false)
        {
            int projectId = dataBaseOperationService.GetProjectIdByChatRoomId(chatRoomId) ?? throw Debug.ShouldNotHappen();

            List<LlmMessageSimp> llmMessages = [];

            foreach (ChatMessageData chatMessageData in chatMessageDatas)
            {
                llmMessages.Add(new LlmMessageSimp(chatMessageData.role, chatMessageData.content));
            }

            string chatHistory = JsonConvert.SerializeObject(llmMessages);

            LlmStreamSlice? response;
            string llmModel = DefaultModelConfigModel.DefaultModelConfig.RenameChatRoomModel;
            response = await llmComEntryPoint.RequestChatSimp(PromptMessagesConsts.RoomRenamingMessages(chatHistory), llmModel);

            if (response == null || response.message == null)
            {
                return;
            }

            dataBaseOperationService.RenameChatRoom(chatRoomId, response.message);

            List<string> connections = userCookieDataModel.GetConnectionsByUserId(userId, UserCookieDataModel.HubConnectionClassEnum.UserSettings);

            foreach (string connectionId in connections)
            {
                await SignalRUserSettingsHub.RenameChatRoomApiResponse(userSettingsHubContext, connectionId, projectId, chatRoomId, response.message);
            }
        }
    }

    
    private Dictionary<int, string> SaveUserInputImages(Dictionary<string, string> images)
    {
        Dictionary<int, string> imagePaths = [];

        foreach (KeyValuePair<string, string> image in images)
        {
            string guidString = Guid.NewGuid().ToString()[..8];
            string fileName = guidString + image.Key;

            int imageId = dataBaseOperationService.AddFile(fileName, image.Value);

            imagePaths.Add(imageId, image.Key);
        }

        return imagePaths;
    }

    private Dictionary<int, string> SaveUserInputTextFiles(Dictionary<string, string> textFiles)
    {
        Dictionary<int, string> textFilePaths = [];

        foreach (KeyValuePair<string, string> file in textFiles)
        {
            string guidString = Guid.NewGuid().ToString()[..8];
            string fileName = guidString + file.Key;

            int fileId = dataBaseOperationService.AddFile(fileName, file.Value);

            textFilePaths.Add(fileId, file.Key);
        }

        return textFilePaths;
    }

    private CancellationToken AddCancellationToken(string connectionId)
    {
        if (_cancellationTokenSources.ContainsKey(connectionId) == false)
        {
            _cancellationTokenSources[connectionId] = new CancellationTokenSource();
        }

        CancellationToken cancellationToken = _cancellationTokenSources[connectionId].Token;

        return cancellationToken;
    }

    private List<MessageDB> GetHistoryBranchBasedOnLastMessage(int chatRoomId)
    {
        List<MessageDB> messageDBList = [];
        int lastMessageId = dataBaseOperationService.GetChatRoomLastMessageId(chatRoomId);

        if (lastMessageId > 0)
        {
            MessageDB lastMessageInChatRoom = dataBaseOperationService.GetMessageById(lastMessageId) ?? throw Debug.ShouldNotHappen();
            foreach (int msgId in lastMessageInChatRoom.HistoryMessagesIdList)
            {
                MessageDB messageDB = dataBaseOperationService.GetMessageById(msgId) ?? throw Debug.ShouldNotHappen();
                messageDBList.Add(messageDB);
            }
            messageDBList.Add(lastMessageInChatRoom);

            return messageDBList;
        }
        else
        {
            return [];
        }
    }

    private List<ChatMessageData> CheckToolCall(List<ChatMessageData> chatMessageDataList, bool isNextMessageTool)
    {
        List<string> toolCallIds = [];
        for (int i = chatMessageDataList.Count - 1; i >= 0; i--)
        {
            string? toolCallId = chatMessageDataList[i].toolCallId;
            if (toolCallId != null)
            {
                toolCallIds.Add(toolCallId);
            }

            List<ToolCall>? toolCalls = chatMessageDataList[i].toolCalls;
            if (toolCalls != null)
            {
                List<string> ids = toolCalls?.Select(tc => tc.Id?.ToString() ?? "").ToList() ?? throw Debug.ShouldNotHappen();
                foreach (string id in ids)
                {
                    if (toolCallIds.Remove(id) == false && isNextMessageTool == false)
                    {
                        chatMessageDataList[i].toolCalls = null;
                        string toolCall = JsonConvert.SerializeObject(chatMessageDataList[i].toolCalls);
                        dataBaseOperationService.RemoveToolCallsByAssistantMessageId(chatMessageDataList[i].Id, chatMessageDataList[i].content, toolCall);
                    }
                }
            }

        }
        if (toolCallIds.Count > 0)
        {
            Debug.LogError("Damn! Tool call results are more than tool calls!");
        }
        return chatMessageDataList;
    }

    private List<ChatMessageData> PrepMessageList(int chatRoomId, int userId, string userName, AgentTemplateDB agentTemplate, string model, string osSystemMessage, bool isNextMessageTool)
    {
        DateTime nowTime = DateTime.Now;
        List<ChatMessageData> chatMessages = [];

        int projectId = dataBaseOperationService.GetProjectIdByChatRoomId(chatRoomId) ?? throw Debug.ShouldNotHappen();
        string projectSettingMessage = dataBaseOperationService.FetchProjectSettings(userId, projectId);

        if (string.IsNullOrEmpty(osSystemMessage) == false)
        {
            chatMessages.Add(ChatMessageData.FromSystem(0, 0, osSystemMessage, [], MessageStringTool.CalculateTokens(model, osSystemMessage), nowTime));
        }

        if (string.IsNullOrEmpty(projectSettingMessage) == false)
        {
            chatMessages.Add(ChatMessageData.FromSystem(0, 0, projectSettingMessage, [], MessageStringTool.CalculateTokens(model, projectSettingMessage), nowTime));
        }

        List<PresetMessageDisp>? agentTemplates = null;
        try
        {
            agentTemplates = SerializableObjectBase.FromJson<List<PresetMessageDisp>>(agentTemplate.PresetMessage);
        }
        catch
        {
            chatMessages.Add(ChatMessageData.FromSystem(0, 0, agentTemplate.PresetMessage, [], MessageStringTool.CalculateTokens(model, agentTemplate.PresetMessage), nowTime));
        }

        if (agentTemplates != null)
        {
            foreach (PresetMessageDisp msg in agentTemplates)
            {
                string author = "";
                if (msg.Role == StaticValues.ChatMessageRoles.User)
                {
                    author = userName;
                }
                else if (msg.Role == StaticValues.ChatMessageRoles.Assistant)
                {
                    author = agentTemplate.Name;
                }
                else if (msg.Role == StaticValues.ChatMessageRoles.System)
                {
                    author = StaticValues.ChatMessageRoles.System;
                }
                else
                {
                    throw Debug.ShouldNotHappen();
                }
                
                chatMessages.Add(new ChatMessageData(0, 0, msg.Role, msg.Message, [], author, [], [], null, MessageStringTool.CalculateTokens(model, agentTemplate.PresetMessage), true, nowTime));
            }
        }

        List<MessageDB> messageDBList = GetHistoryBranchBasedOnLastMessage(chatRoomId);

        if (messageDBList.Count == 0)
        {
            return chatMessages;
        }

        foreach (MessageDB messageDB in messageDBList)
        {
            List<FilesDB> filesDb = [];
            Dictionary<string, string> images = [];
            Dictionary<string, string> textFiles = [];

            foreach (int imageId in messageDB.ImageList.Keys)
            {
                FilesDB filesDB = dataBaseOperationService.GetFileById(imageId) ?? throw Debug.ShouldNotHappen();
                images.Add(filesDB.FileName, filesDB.Content);
                filesDb.Add(filesDB);
            }

            foreach (int textFileId in messageDB.TextFileList.Keys)
            {
                FilesDB filesDB = dataBaseOperationService.GetFileById(textFileId) ?? throw Debug.ShouldNotHappen();
                textFiles.Add(filesDB.FileName, filesDB.Content);
                filesDb.Add(filesDB);
            }            

            int token = MessageStringTool.CalculateTokens(model, messageDB.Content + messageDB.ToolCalls, images, textFiles);
            chatMessages.Add(ChatMessageData.FromMessageDB(messageDB, token, filesDb));
        }

        chatMessages = CheckToolCall(chatMessages, isNextMessageTool);

        chatMessages = CutMessages(chatMessages, modelInfos.Values.FirstOrDefault(m => m.ModelName == model)?.ContextWindow ?? throw Debug.ShouldNotHappen());

        if (chatMessages.Count == 0)
        {
            return [];
        }

        return chatMessages;
    }

    private async Task SendMessageToLLM(string ConnectionId, int chatRoomId, int userId, AgentTemplateDB agentTemplate, int agentInstanceId, string model, List<ChatMessageData> chatMessages, List<ToolDefinition>? toolsAvailable = null, CancellationToken cancellationToken = default)
    {
        StringBuilder accumulatedResponse = new();
        string? toolCallsOutJson = null;

        List<int> historyMessagesIdList = chatMessages
                .Where(m => m.role != StaticValues.ChatMessageRoles.System && m.Id != 0)
                .Select(m => m.Id)
                .ToList();

        //Should be overwritten when stream is completed
        int agentMsgId = dataBaseOperationService.AddMessageToChatRoom(chatRoomId, userId, agentInstanceId, StaticValues.ChatMessageRoles.Assistant, agentTemplate.Name, MessageConsts.InitRespondMessage, historyMessagesIdList, [], [], DateTime.Now);
        dataBaseOperationService.UpdateChatRoomLastMessage(chatRoomId, agentMsgId);

        //TODO:Author need to be provided(agent name?)

        //pick the tools that agent can use
        List<ToolDefinition>? allowedTools = toolsAvailable?.Where(ta => agentTemplate.AllowedTools.Contains(ta.Function?.Name ?? "NullFunction")).ToList();
        if (allowedTools?.Count == 0)
        {
            allowedTools = null;
        }

        //check if user disabled room's tool call, if so, disable default agent tool call
        if (agentTemplate.Name == NameConsts.DefaultAgentName)
        {
            var chatRoom = dataBaseOperationService.GetChatRoom(chatRoomId);
            bool isAllowToolCall = dataBaseOperationService.GetIsAllowToolCallByUserId(userId);
            if (isAllowToolCall == false)
            {
                allowedTools = null;
            }
        }

        int sliceIndex = 0;
        bool isResponsed = false;

        try
        {
            for (int tryTime = 0; tryTime < 2 && isResponsed == false; tryTime++)
            {
                isResponsed = false;
                IAsyncEnumerable<LlmStreamSlice?> responseStream;
                responseStream = llmComEntryPoint.RequestChatAsStream(chatMessages, model, agentTemplate.Temperature, allowedTools);

                await foreach (var response in responseStream)
                {
                    cancellationToken.ThrowIfCancellationRequested();
                    // api calling failed
                    if (isResponsed == false && response == null)
                    {
                        break;
                    }
                    else if (response == null)
                    {
                        // If a null value is received, break out of the loop.
                        break;
                    }

                    isResponsed = true;

                    if (response.toolCallsOut != null)
                    {
                        toolCallsOutJson = SerializableObjectBase.ConvertToJson(response.toolCallsOut);
                    }

                    if (response.message != null || toolCallsOutJson != null)
                    {
                        // Publish each piece of the response to the user's channel
                        await SignalRChatHub.SendUserMessageApiAgentResponse(signalRChatHubContext, ConnectionId, userId, chatRoomId, agentMsgId, sliceIndex, historyMessagesIdList, agentTemplate.Name, response.message, toolCallsOutJson);
                        sliceIndex++;
                    }

                    accumulatedResponse.Append(response.message);
                }
            }

            if (isResponsed == false)
            {
                await SignalRChatHub.SendUserMessageApiAgentResponse(signalRChatHubContext, ConnectionId, userId, chatRoomId, agentMsgId, sliceIndex, historyMessagesIdList, agentTemplate.Name, MessageConsts.NoneRespoenseMessage, toolCallsOutJson);
                sliceIndex++;
            }
            else if (string.IsNullOrEmpty(toolCallsOutJson))
            {
                _ = ProvideAssistentOptionsIfNotToolCall(ConnectionId, chatRoomId, chatMessages, accumulatedResponse.ToString(), 4);
            }
        }
        catch (OperationCanceledException) { }
        finally
        {
            //has respond
            if (accumulatedResponse.Length > 0 || string.IsNullOrEmpty(toolCallsOutJson) == false)
            {
                //add the entire msg to db
                dataBaseOperationService.UpdateMessageContent(agentMsgId, accumulatedResponse.ToString(), toolCallsOutJson, DateTime.Now);

                //request renaming
                if (historyMessagesIdList.Count > 2)
                {
                    chatMessages.Add(new ChatMessageData() { content = accumulatedResponse.ToString(), role = StaticValues.ChatMessageRoles.Assistant });
                    _ = RenameRoomIfHaveNot(userId, chatRoomId, chatMessages);
                }
            }

            // This assumes that null values signify a condition to stop processing further.
            await SignalRChatHub.SendUserMessageApiAgentResponse(signalRChatHubContext, ConnectionId, userId, chatRoomId, agentMsgId, sliceIndex, historyMessagesIdList, agentTemplate.Name, null, null);
        }
    }

#region Commands
    private async Task SendToolCallResultRequestHandlerAsync(SendToolResultCommand cmd)
    {
        if (userCookieDataModel.CheckUserAndSession(cmd.UserId, cmd.SessionId) == false)
        {
            return;
        }

        try
        {
            MessageDB toolCallMessage = dataBaseOperationService.GetMessageById(cmd.ToolCallMessageId) ?? throw Debug.ShouldNotHappen();

            int chatRoomId = toolCallMessage.ChatRoomId;

            string oneOfToolCallResult = cmd.ToolCallResults[cmd.ToolCallResults.Keys.First()];
            string agentTemplateName = MessageStringTool.GetAgentMentionFromMessage(oneOfToolCallResult);
            AgentTemplateDB agentTemplate = dataBaseOperationService.GetAgentTemplateByName(cmd.UserId, agentTemplateName) ?? dataBaseOperationService.GetAgentTemplateByName(cmd.UserId, NameConsts.DefaultAgentName) ?? throw Debug.ShouldNotHappen();

            //Get AgentInstace, if failed to find, create a instance to that room
            int agentInstanceId = dataBaseOperationService.GetAgentInstanceIdInRoom(chatRoomId, agentTemplate.Id);
            if (agentInstanceId == -1)
            {
                throw Debug.ShouldNotHappen();
            }

            List<ChatMessageData> messageList = PrepMessageList(chatRoomId, cmd.UserId, cmd.UserName, agentTemplate, cmd.Model, cmd.OsSystemMsg, true);
            if (messageList.Count == 0)
            {
                return;
            }
            List<int> historyMessagesIdList = messageList
                        .Where(m => m.role != StaticValues.ChatMessageRoles.System && m.Id != 0)
                        .Select(m => m.Id)
                        .ToList();

            if (agentTemplate == null)
            {
                Debug.LogError("Failed to find agentTemplate");
                return;
            }
            else if (agentTemplate.Id == 1) // if use default agent
            {
                List<string> accessibleLlmModels = dataBaseOperationService.FetchUserAccessibleLlmModels(cmd.UserId);
                if (accessibleLlmModels.Contains(cmd.Model))
                {
                    agentTemplate.LLMModel = cmd.Model;
                }
                else
                {
                    

                    foreach (var toolCallResult in cmd.ToolCallResults)
                    {
                        //Add message to db
                        int incomingMessageId = dataBaseOperationService.AddToolMessageToChatRoom(chatRoomId, cmd.UserId, agentInstanceId, toolCallResult.Key, toolCallResult.Value, historyMessagesIdList);
                        historyMessagesIdList.Add(incomingMessageId);
                        await SignalRChatHub.SendToolResultApiResponse(signalRChatHubContext, cmd.ConnectionId, incomingMessageId, cmd.UserId, chatRoomId, toolCallResult.Key, toolCallResult.Value, historyMessagesIdList);
                    }

                    int agentMsgId = dataBaseOperationService.AddMessageToChatRoom(chatRoomId, cmd.UserId, agentInstanceId, StaticValues.ChatMessageRoles.Assistant, agentTemplate.Name, "Unauthorized model.", [], [], [], DateTime.Now);
                    dataBaseOperationService.UpdateChatRoomLastMessage(chatRoomId, agentMsgId); 
                    
                    await SignalRChatHub.SendUserMessageApiAgentResponse(signalRChatHubContext, cmd.ConnectionId, cmd.UserId, chatRoomId, agentMsgId, 0, historyMessagesIdList, "System", "Unauthorized model.", "");
                    await SignalRChatHub.SendUserMessageApiAgentResponse(signalRChatHubContext, cmd.ConnectionId, cmd.UserId, chatRoomId, agentMsgId, 1, null, "System", null, "");
                    return;
                }
            }

            foreach (var toolCallResult in cmd.ToolCallResults)
            {
                //Add message to db
                int incomingMessageId = dataBaseOperationService.AddToolMessageToChatRoom(chatRoomId, cmd.UserId, agentInstanceId, toolCallResult.Key, toolCallResult.Value, historyMessagesIdList);
                dataBaseOperationService.UpdateChatRoomLastMessage(chatRoomId, incomingMessageId); 
                historyMessagesIdList.Add(incomingMessageId);
                await SignalRChatHub.SendToolResultApiResponse(signalRChatHubContext, cmd.ConnectionId, incomingMessageId, cmd.UserId, chatRoomId, toolCallResult.Key, toolCallResult.Value, historyMessagesIdList);
            }

            CancellationToken cancellationToken = AddCancellationToken(cmd.ConnectionId);

            messageList = PrepMessageList(chatRoomId, cmd.UserId, cmd.UserName, agentTemplate, cmd.Model, cmd.OsSystemMsg, false);
            if (messageList.Count == 0)
            {
                return;
            }

            await SendMessageToLLM(cmd.ConnectionId, chatRoomId, cmd.UserId, agentTemplate, agentInstanceId, cmd.Model, messageList, null, cancellationToken);
        }
        catch (Exception ex)
        {
            await SignalRChatHub.SendToolResultApiResponse(signalRChatHubContext, cmd.ConnectionId, cmd.UserId, -1, -1, "NULL", "NULL", []);
            Debug.LogError(ex?.ToString() ?? "Unknown error");
        }
        finally
        {
            _cancellationTokenSources.Remove(cmd.ConnectionId);
        }
    }

    private async Task ResendMessageCommandHandlerAsync(ResendMessageCommand cmd)
    {
        if (userCookieDataModel.CheckUserAndSession(cmd.UserId, cmd.SessionId) == false)
        {
            return;
        }

        MessageDB messageDB;
        int queryId = cmd.MessageId;
        do
        {
            messageDB = dataBaseOperationService.GetMessageById(queryId) ?? throw Debug.ShouldNotHappen();
            queryId = messageDB.HistoryMessagesIdList.LastOrDefault();
        } while (messageDB.Role != StaticValues.ChatMessageRoles.User && queryId != default);

        string content = string.IsNullOrEmpty(cmd.Content) ? messageDB.Content : cmd.Content;

        Dictionary<string, string> images = [];
        foreach (KeyValuePair<int, string> image in messageDB.ImageList)
        {
            FilesDB imageDB = dataBaseOperationService.GetFileById(image.Key) ?? throw Debug.ShouldNotHappen();
            images.Add(imageDB.FileName[8..], imageDB.Content);
        }

        Dictionary<string, string> textFiles = [];
        foreach (KeyValuePair<int, string> file in messageDB.TextFileList)
        {
            FilesDB fileDB = dataBaseOperationService.GetFileById(file.Key) ?? throw Debug.ShouldNotHappen();
            textFiles.Add(fileDB.FileName[8..], fileDB.Content);
        }

        dataBaseOperationService.UpdateChatRoomLastMessage(messageDB.ChatRoomId, messageDB.HistoryMessagesIdList.LastOrDefault());

        await SendUserMsgRequestHandlerAsync(new SendUserMsgCommand(cmd.ConnectionId, cmd.SessionId, cmd.UserId, messageDB.Author, messageDB.ChatRoomId,
            cmd.OsSystemMsg, content, images, textFiles, cmd.Model, cmd.ToolSetJson));
    }

    private async Task OverrideMessageCommandHandlerAsync(OverrideMessageCommand cmd)
    {
        if (userCookieDataModel.CheckUserAndSession(cmd.UserId, cmd.SessionId) == false)
        {
            return;
        }

        dataBaseOperationService.UpdateMessageContent(cmd.MessageId, cmd.Content, null, DateTime.Now);

        await SignalRChatHub.OverrideMessageApiResponse(signalRChatHubContext, cmd.ConnectionId, cmd.MessageId, cmd.Content);
    }

    private async Task SendUserMsgRequestHandlerAsync(SendUserMsgCommand cmd)
    {
        if (userCookieDataModel.CheckUserAndSession(cmd.UserId, cmd.SessionId) == false)
        {
            return;
        }

        try
        {
            string agentTemplateName = MessageStringTool.GetAgentMentionFromMessage(cmd.UserMessage);
            AgentTemplateDB agentTemplate = dataBaseOperationService.GetAgentTemplateByName(cmd.UserId, agentTemplateName) ?? dataBaseOperationService.GetAgentTemplateByName(cmd.UserId, NameConsts.DefaultAgentName) ?? throw Debug.ShouldNotHappen();

            string model; 
            if (agentTemplateName != NameConsts.DefaultAgentName)
            {
                model = agentTemplate.LLMModel;
            }
            else
            {
                model = cmd.Model;
            }

            if (CheckModelAuthorization(cmd.UserId, model) == false)
            {
                return;
            }

            int agentInstanceId = CreateAgentInstanceIfNotExist(cmd.RoomId, agentTemplate);

            Dictionary<int, string> imageIdAndName = [];
            Dictionary<int, string> textFileIdAndName = [];
            if (cmd.Images.Count > 0)
            {
                imageIdAndName = SaveUserInputImages(cmd.Images);
            }

            Dictionary<string, string> a = imageIdAndName.ToDictionary(image => image.Value, image => "");
            if (cmd.TextFiles.Count > 0)
            {
                textFileIdAndName = SaveUserInputTextFiles(cmd.TextFiles);
            }

            DateTime dateTime = DateTime.Now;
            int tokens = MessageStringTool.CalculateTokens(model, cmd.UserMessage, cmd.Images, cmd.TextFiles);

            List<ChatMessageData> messageList = PrepMessageList(cmd.RoomId, cmd.UserId, cmd.UserName, agentTemplate, model, cmd.OsSystemMsg, false);

            List<int> historyMessagesIdList = messageList
                .Where(m => m.role != StaticValues.ChatMessageRoles.System && m.Id != 0)
                .Select(m => m.Id)
                .ToList();

            int userMsgId = dataBaseOperationService.AddMessageToChatRoom(cmd.RoomId, cmd.UserId, agentInstanceId, StaticValues.ChatMessageRoles.User, cmd.UserName, cmd.UserMessage, historyMessagesIdList, imageIdAndName, textFileIdAndName, dateTime);
            dataBaseOperationService.UpdateChatRoomLastMessage(cmd.RoomId, userMsgId);

            ChatMessageData userChatMessageData = new(userMsgId, cmd.UserId, StaticValues.ChatMessageRoles.User, cmd.UserMessage, historyMessagesIdList, cmd.UserName, imageIdAndName.ToDictionary(image => image.Key, image => (image.Value, cmd.Images[image.Value])), textFileIdAndName.ToDictionary(file => file.Key, file => (file.Value, cmd.TextFiles[file.Value])), null, tokens, true, dateTime);
            // not sending back previous mentioned agent
            await SignalRChatHub.SendUserMessageApiUserResponse(signalRChatHubContext, cmd.ConnectionId, cmd.RoomId, userChatMessageData, agentTemplate.Name == NameConsts.DefaultAgentName ? "" : agentTemplate.Name);

            CancellationToken cancellationToken = AddCancellationToken(cmd.ConnectionId);

            if (messageList.Count == 0) 
            {
                return;
            }

            messageList.Add(userChatMessageData);
            await SendMessageToLLM(cmd.ConnectionId, cmd.RoomId, cmd.UserId, agentTemplate, agentInstanceId, model, messageList, cmd.ToolSet, cancellationToken);
        }
        catch (Exception ex)
        {
            await SignalRChatHub.SendUserMessageApiUserResponse(signalRChatHubContext, cmd.ConnectionId, -1, new ChatMessageData(), "");
            await SignalRChatHub.SendUserMessageApiAgentResponse(signalRChatHubContext, cmd.ConnectionId, cmd.UserId, -1, -1, -1, null, "", null, "");
            Debug.LogError(ex?.ToString() ?? "Unknown error");
        }
        finally
        {
            _cancellationTokenSources.Remove(cmd.ConnectionId);
        }
    }

    private async Task SendUserMessageWithoutLLMResponseRequestHandlerAsync(SendUserMessageWithoutLLMResponseCommand cmd)
    {
        if (userCookieDataModel.CheckUserAndSession(cmd.UserId, cmd.SessionId) == false)
        {
            return;
        }

        try
        {
            AgentTemplateDB agentTemplate = new();
            //create user message

            int agentId = dataBaseOperationService.GetDefaultAgentIdByChatRoomID(cmd.RoomId);
            DateTime dateTime = DateTime.Now;

            List<MessageDB> messageDBList = GetHistoryBranchBasedOnLastMessage(cmd.RoomId);
            List<int> historyMessagesIdList = messageDBList
                .Where(m => m.Role != StaticValues.ChatMessageRoles.System && m.Id != 0)
                .Select(m => m.Id)
                .ToList();

            int userMsgId = dataBaseOperationService.AddMessageToChatRoom(cmd.RoomId, cmd.UserId, agentId, StaticValues.ChatMessageRoles.User, cmd.UserName, cmd.UserMessage, historyMessagesIdList, [], [], dateTime);
            dataBaseOperationService.UpdateChatRoomLastMessage(cmd.RoomId, userMsgId);

            ChatMessageData userChatMessageData = new(userMsgId, cmd.UserId, StaticValues.ChatMessageRoles.User, cmd.UserMessage, historyMessagesIdList, cmd.UserName, [], [], null, 0, true, dateTime);
            // not sending back previous mentioned agent
            await SignalRChatHub.SendUserMessageApiUserResponse(signalRChatHubContext, cmd.ConnectionId, cmd.RoomId, userChatMessageData, "");
        }
        catch (Exception ex)
        {
            await SignalRChatHub.SendUserMessageApiUserResponse(signalRChatHubContext, cmd.ConnectionId, -1, new ChatMessageData(), "");
            Debug.LogError(ex?.ToString() ?? "Unknown error");
        }
    }

    private async Task SendNodeMessageRequestHandlerAsync(SendNodeMessageCommand cmd)
    {
        if (userCookieDataModel.CheckUserAndSession(cmd.UserId, cmd.SessionId) == false)
        {
            return;
        }

        int nodeMessageId = 0;
        try
        {
            int agentId = dataBaseOperationService.GetDefaultAgentIdByChatRoomID(cmd.RoomId);

            List<MessageDB> messageDBList = GetHistoryBranchBasedOnLastMessage(cmd.RoomId);
            List<int> historyMessagesIdList = messageDBList
                .Where(m => m.Role != StaticValues.ChatMessageRoles.System && m.Id != 0)
                .Select(m => m.Id)
                .ToList();

            nodeMessageId = dataBaseOperationService.AddMessageToChatRoom(cmd.RoomId, cmd.UserId, agentId, StaticValues.ChatMessageRoles.Assistant, cmd.UserName, cmd.Message, historyMessagesIdList, [], [], DateTime.Now);
            dataBaseOperationService.UpdateChatRoomLastMessage(cmd.RoomId, nodeMessageId);

            await SignalRChatHub.SendUserMessageApiAgentResponse(signalRChatHubContext, cmd.ConnectionId, cmd.UserId, cmd.RoomId, nodeMessageId, 0, historyMessagesIdList, cmd.WorkflowName, cmd.Message);
        }
        catch (Exception ex)
        {
            await SignalRChatHub.SendUserMessageApiAgentResponse(signalRChatHubContext, cmd.ConnectionId, cmd.UserId, -1, -1, -1, null, "", "");
            Debug.LogError(ex?.ToString() ?? "Unknown error");
        }
        finally
        {
            // pass 1 to sliceIndex, send second respoense
            await SignalRChatHub.SendUserMessageApiAgentResponse(signalRChatHubContext, cmd.ConnectionId, cmd.UserId, cmd.RoomId, nodeMessageId, 1, null, cmd.WorkflowName, null);
        }
    }

    private void StopUserMsgRequestHandler(StopUserMsgCommnad command)
    {
        if (userCookieDataModel.CheckUserAndSession(command.UserId, command.SessionId) == false)
        {
            return;
        }

        if (_cancellationTokenSources.TryGetValue(command.ConnectionId, out CancellationTokenSource? value))
        {
            value.Cancel();
            _cancellationTokenSources.Remove(command.ConnectionId);
        }
    }

    private async Task GetFileContentRequestHandlerAsync(GetFileContentCommand cmd)
    {
        if (userCookieDataModel.CheckUserAndSession(cmd.UserId, cmd.SessionId) == false)
        {
            return;
        }

        FilesDB file = dataBaseOperationService.GetFileContent(cmd.ImageId) ?? throw Debug.ShouldNotHappen();

        string extension = Path.GetExtension(file.FileName).ToLowerInvariant();
        string fileType = "";
        string[] readableImageExtensions = { ".png", ".jpg", ".jpeg", ".webp" };
        string[] readableTextExtensions = {
                ".txt", ".csv", ".json", ".xml", ".htm", ".md", ".ini", ".yaml", ".yml", ".log", ".rtf",
                ".c", ".cpp", ".cc", ".cxx", ".cs", ".java", ".py", ".js", ".ts", ".html", ".css", ".php", ".rb", ".go",
                ".swift", ".kt", ".m", ".sh", ".bat", ".pl", ".vb", ".sql", ".r", ".lua"
            };

        if (readableImageExtensions.Contains(extension))
        {
            fileType = "image";
        }
        else if (readableTextExtensions.Contains(extension))
        {
            fileType = "text";
        }

        await SignalRChatHub.GetFileContentApiResponse(signalRChatHubContext, cmd.ConnectionId, file.Id, fileType, file.Content);
    }
#endregion
}